﻿/*************************************************************************************
 * 文 件 名:   HK_Foldup
 * 
 * 描    述:   Inspector序列化字段自动折叠特性
 * 
 * 版    本：  V1.0
 * 创 建 者：  京产肠饭
 * 创建时间：  2022/12/19
 * 链   接：  https://gitee.com/JingChanChangFan/hk_-unity-tools
*************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(MonoBehaviour), true, isFallback = true)]
[CanEditMultipleObjects]
public class HK_Foldup : Editor
{
    Dictionary<string, CacheFoldProp> cacheFoldsDic = new Dictionary<string, CacheFoldProp>();
    List<SerializedProperty> propsList = new List<SerializedProperty>();

    bool isInit;

    void OnEnable()
    {
        isInit = false;
    }

    void OnDisable()
    {
        if (target == null)
            return;

        foreach (var keyValuePair in cacheFoldsDic)
        {
            keyValuePair.Value.Dispose();
        }
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        if (!isInit)
        {
            List<FieldInfo> fieldsList = GetFields();
            HK_FoldupAttribute foldupAttr = default;

            for (int i = 0; i < fieldsList.Count; i++)
            {
                var foldup = Attribute.GetCustomAttribute(fieldsList[i], typeof(HK_FoldupAttribute)) as HK_FoldupAttribute;
                CacheFoldProp cache;

                if (foldup == null)
                {
                    if (foldupAttr != null && foldupAttr.isFoldEverything)
                    {
                        if (!cacheFoldsDic.TryGetValue(foldupAttr.name, out cache))
                        {
                            cacheFoldsDic.Add(foldupAttr.name, new CacheFoldProp { attr = foldupAttr, types = new HashSet<string> { fieldsList[i].Name } });
                        }
                        else
                        {
                            cache.types.Add(fieldsList[i].Name);
                        }
                    }

                    continue;
                }

                foldupAttr = foldup;

                if (!cacheFoldsDic.TryGetValue(foldup.name, out cache))
                {
                    var expanded = EditorPrefs.GetBool(string.Format($"{foldup.name}{fieldsList[i].Name}{target.GetInstanceID()}"), false);
                    cacheFoldsDic.Add(foldup.name, new CacheFoldProp { attr = foldup, types = new HashSet<string> { fieldsList[i].Name }, isExpanded = expanded });
                }
                else
                {
                    cache.types.Add(fieldsList[i].Name);
                }
            }

            var property = serializedObject.GetIterator();
            var next = property.NextVisible(true);
            if (next)
            {
                do
                {
                    HandleFoldProp(property);
                } while (property.NextVisible(false));
            }

            isInit = true;
        }

        if (propsList.Count == 0)
        {
            DrawDefaultInspector();
            return;
        }

        // 脚本的标题
        using (new EditorGUI.DisabledScope("m_Script" == propsList[0].propertyPath))
        {
            EditorGUILayout.Space();
            EditorGUILayout.PropertyField(propsList[0], true);
            EditorGUILayout.Space();
        }

        // 脚本的各类变量
        foreach (var pair in cacheFoldsDic)
        {
            EditorGUILayout.BeginVertical(new GUIStyle(GUI.skin.box));
            Foldout(pair.Value);
            EditorGUILayout.EndVertical();
            EditorGUI.indentLevel = 0;
        }

        EditorGUILayout.Space();

        for (int i = 1; i < propsList.Count; i++)
        {
            EditorGUILayout.PropertyField(propsList[i], true);
        }

        EditorGUILayout.Space();

        serializedObject.ApplyModifiedProperties();
    }

    List<FieldInfo> GetFields()
    {
        Dictionary<int, List<FieldInfo>> fields = new Dictionary<int, List<FieldInfo>>();
        List<FieldInfo> objectFields = new List<FieldInfo>();

        var t = target.GetType();
        var hash = t.GetHashCode();

        if (!fields.TryGetValue(hash, out objectFields))
        {
            var typeTree = new List<Type>();
            while (t.BaseType != null)
            {
                typeTree.Add(t);
                t = t.BaseType;
            }

            objectFields = target.GetType()
                    .GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic)
                    .OrderByDescending(x => typeTree.IndexOf(x.DeclaringType))
                    .ToList();
            fields.Add(hash, objectFields);
        }

        return objectFields;
    }

    void Foldout(CacheFoldProp cache)
    {
        cache.isExpanded = EditorGUILayout.Foldout(cache.isExpanded, cache.attr.name, true, new GUIStyle(EditorStyles.foldout));

        if (cache.isExpanded)
        {
            EditorGUI.indentLevel = 1;

            for (int i = 0; i < cache.props.Count; i++)
            {
                EditorGUILayout.BeginVertical(new GUIStyle(GUI.skin.box));
                EditorGUILayout.PropertyField(cache.props[i], new GUIContent(ObjectNames.NicifyVariableName(cache.props[i].name)), true);
                EditorGUILayout.EndVertical();
            }
        }
    }

    void HandleFoldProp(SerializedProperty prop)
    {
        bool shouldBeFolded = false;

        foreach (var pair in cacheFoldsDic)
        {
            if (pair.Value.types.Contains(prop.name))
            {
                var pr = prop.Copy();
                shouldBeFolded = true;
                pair.Value.props.Add(pr);

                break;
            }
        }

        if (shouldBeFolded == false)
        {
            var pr = prop.Copy();
            propsList.Add(pr);
        }
    }

    class CacheFoldProp
    {
        public HashSet<string> types = new HashSet<string>();
        public List<SerializedProperty> props = new List<SerializedProperty>();
        public HK_FoldupAttribute attr;
        public bool isExpanded;

        public void Dispose()
        {
            props.Clear();
            types.Clear();
            attr = null;
        }
    }
}
